package Week6;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;




//====================================================================================
//GUI class
//====================================================================================

//An over-simplified GUI for drawing.
@SuppressWarnings("serial") //Just to suppress a warning
public class OurGUI extends JPanel implements ActionListener {

	//Member variables.
	private PointBuffer pb, pbPln;
	private PolyLine pln;
	private Point pnt, pUser;
	private JTextField rField, addField;
	private double pbRad;
	private Polygon plg;
	private Rectangle rct;
	private Square sqr;
	private Circle cir;
	private BoundingBox bbPln, bbPb, bbPnt, bbPlg, bbRct, bbSqr, bbCir, bbAll;
	private ArrayList<Point> boundingBoxList;
	protected int userX, userY;
	
	//Constructor for OurGUI.
	public OurGUI() {
		
		//Calls the constructor of JPanel to enable DoubleBuffering to avoid flickering.
		super(true); 
		
		//Set the (preferred) size of the panel
		//Hard-coded? What about objects outside of this panel size?
		//Could the window be flexible to enclose all object?
		//Or, should we simply use scroll bars to see objects outside of initial view?
		setPreferredSize(new Dimension(500,500));
	}

	
	
	


	
//====================================================================================
//ACTIONS
//====================================================================================


	//Multi-listener using if-statements dependent upon the Jbutton clicked.
	public void actionPerformed(ActionEvent arg0){
		
		//Action for clicking "Snap PolyLine closed if within PointBuffer" button.		
		if (arg0.getActionCommand() == "Snap PolyLine closed if within PointBuffer"){
		
			System.out.println("");
			System.out.println("--------------------");
			System.out.println("Clicked snap button.");
			//Get user specified radius for PointBuffer from rField component.
			pbRad = (double) (Integer.decode(rField.getText())); //Works for integer inputs, not doubles.
			//Create PointBuffer.
			pbPln = new PointBuffer(pln.polypoints.get(0), pbRad);
			System.out.println("");
			System.out.println("User specified snap radius:");
			System.out.println(pbRad);
			System.out.println("");
			System.out.println("Start point of PolyLine:");
			System.out.println(pln.polypoints.get(0));
			System.out.println("");
			System.out.println("Original end point of PolyLine:");
			System.out.println(pln.polypoints.get(pln.polypoints.size()-1));
			System.out.println("");
			System.out.println("Distance from start to end point of PolyLine:");
			System.out.println(pln.polypoints.get(0).getDistance(pln.polypoints.get(pln.polypoints.size()-1)));
			
			//Used  <= getDistance between start and end points of PolyLine.
			//An alternative would be to use isInside method to determine if end point was within PointBuffer,
			//but that would not snap closed at exactly the user specified radius because it wouldn't be inside.
			//if (pbPln.isInside(pln.polypoints.get(pln.polypoints.size()-1))){
			if (pln.polypoints.get(0).getDistance(pln.polypoints.get(pln.polypoints.size()-1)) <= pbRad){
				System.out.println("");
				System.out.println("The end point is within the snap radius.");
				System.out.println("The end point was adjusted.");
				
				//Create temporary PolyLine for replacing original with adjusted coordinates.
				PolyLine tempPln = new PolyLine();
				
				//Fill tempPln with original values, except the end point.
				for (int i = 0; i < pln.polypoints.size()-1; i++){
					tempPln.polypoints.add(pln.polypoints.get(i));
				}
				
				//Copy value of PolyLine start point as adjusted end point.
				tempPln.polypoints.add(pln.polypoints.get(0));
				
				//Set tempPln as pln, now with adjusted values.
				pln.setPolypoints(tempPln.polypoints);			
				
				System.out.println("");
				System.out.println("Adjusted end point of PolyLine:");
				System.out.println(pln.polypoints.get(pln.polypoints.size()-1));
	
				System.out.println("");
				System.out.println("PolyLine:");
				for (int i=0; i < pln.polypoints.size(); i++){
					System.out.println(pln.polypoints.get(i));
				}
				
				//Repaint the GUI with the adjusted PolyLine.
				repaint();
						
			}
					
			else {
				//If the PolyLine end point is not within the user specified radius, do nothing.
				System.out.println("");
				System.out.println("The end point is not within the snap radius.");
				System.out.println("The end point was not adjusted.");
			}
		}
		
			
		//Action for clicking "Create BoundingBoxes" button.
		if (arg0.getActionCommand() == "Create BoundingBoxes"){

			//Create bounding boxes for each object.
			//This seems like wasteful hard-coding for specific variables.
			//Thought about implementing bounding box for Geometry
			//but wanted many individual bounding boxes used later
			//to create one bounding box around all bounding boxes.
			bbPln = new BoundingBox(pln.polypoints);
			bbPb = new BoundingBox(pb);
			bbPnt = new BoundingBox(pnt);
			bbPlg = new BoundingBox(plg.polypoints);
			bbRct = new BoundingBox(rct);
			bbSqr = new BoundingBox(sqr);
			bbCir = new BoundingBox(cir);

			//Create ArrayList<Point> to hold all BoundingBox coordinates.
			boundingBoxList = new ArrayList<Point>();
			
			//Add BoundingBox coordinates to ArrayList.
			boundingBoxList.add(bbPnt.getUl());
			boundingBoxList.add(bbPnt.getLr());
			
			boundingBoxList.add(bbPln.getUl());
			boundingBoxList.add(bbPln.getLr());
			
			boundingBoxList.add(bbPlg.getUl());
			boundingBoxList.add(bbPlg.getLr());
			
			boundingBoxList.add(bbPb.getUl());
			boundingBoxList.add(bbPb.getLr());
			
			boundingBoxList.add(bbRct.getUl());
			boundingBoxList.add(bbRct.getLr());
			
			boundingBoxList.add(bbSqr.getUl());
			boundingBoxList.add(bbSqr.getLr());
			
			boundingBoxList.add(bbCir.getUl());
			boundingBoxList.add(bbCir.getLr());
		
//			System.out.println("");
//			System.out.println("List of all BoundingBox points:");
//			System.out.println(boundingBoxList);   
			
			//Create BoundingBox for ArrayList<Point> of all BoundingBoxes.
			bbAll = new BoundingBox(boundingBoxList);
			
			//Repaint GUI with each BoundingBox and one for all BoundingBoxes.
			repaint();
			
		}
		
		//Action for drawing the user-specified point.
		if (arg0.getActionCommand() == "Add Points"){
			
			//Collect the user-specified string of coordinates.
			String coordsText = new String(addField.getText());
			
			//split the coordinates.
			String[] coordsArray = coordsText.split(" ", 2);
			
			//Convert from strings to integers.
			userX = Integer.parseInt(coordsArray[0]);
			userY = Integer.parseInt(coordsArray[1]);
			
			//Create a new user-specified point from integer values.
			pUser = new Point(userX, userY);
			
			//Repaint GUI with the user-specified point.
			repaint();
		}
		
		
	} //End ACTIONS
	
		
		

        

	
	
//====================================================================================
//PAINT
//====================================================================================



	/*
	 * Java takes care of calling this method. For this reason we had to define MinimalDrawingGUI 
	 * as a subclass of JPanel (line 14). You do not need to call the method by hand. If, however, you
	 * change data in your model, e.g., the PointBuffer, you will have to call repaint().
	 * 
	 */
	public void paintComponent(Graphics g) {
        super.paintComponent(g);    // Paints all other stuff, e..g., background. (Is this defined in JPanel?).
        
        // Cast to a Graphics2D object to support affine transformations.
        Graphics2D g2d = (Graphics2D) g;
        //      g2d.shear(0.53 0.15);     
        //      g2d.rotate(0.25);
        //      g2d.scale(2.0, -1.5);

        
       
        //Draw point with oval of radius 2
        //and an offset of 2 (up and left) for centering the oval
        //and a height and width (diameter) of 2*2
        //on the corresponding display coordinates.
        g2d.setColor(Color.blue);        
        g2d.drawOval(
        		(int) pnt.getX()-2,
        		(int) pnt.getY()-2,
        		(int) 2*2,
        		(int) 2*2
        );

        

        
      //Draw Circle (Note: doubles are cast as ints; shape is shifted by half the radius).
        g2d.setColor(Color.magenta);
        g2d.drawOval(
        		(int) cir.getPt().getX() - ((int) cir.getRad()),
        		(int) cir.getPt().getY() - ((int) cir.getRad()), 
        		(int) cir.getRad()*2, 
        		(int) cir.getRad()*2
        );

        
        //Draw PointBuffer (Note: doubles are cast as ints; shape is shifted by half the radius).
        g2d.setColor(Color.red);
        g2d.drawOval(
        		(int) pb.getPt().getX() - ((int) pb.getRad()),
        		(int) pb.getPt().getY() - ((int) pb.getRad()), 
        		(int) pb.getRad()*2, 
        		(int) pb.getRad()*2
        );

        		
		//Draw PolyLine.
        g2d.setColor(Color.green);
        for(int i=0; i < pln.polypoints.size()-1; i++){
			g2d.drawLine(
	        		(int) pln.polypoints.get(i).getX(),
	        		(int) pln.polypoints.get(i).getY(),
	        		(int) pln.polypoints.get(i+1).getX(),
	        		(int) pln.polypoints.get(i+1).getY()
	        );
		}      	
        
        
		//Draw Polygon.
        g2d.setColor(Color.orange);
        for(int i=0; i < plg.polypoints.size()-1; i++){
			g2d.drawLine(
	        		(int) plg.polypoints.get(i).getX(),
	        		(int) plg.polypoints.get(i).getY(),
	        		(int) plg.polypoints.get(i+1).getX(),
	        		(int) plg.polypoints.get(i+1).getY()
	        );
		}
        
        
		//Draw Rectangle.
        g2d.setColor(Color.pink);
		g2d.drawLine(
	        (int) rct.getPt().getX(),
	        (int) rct.getPt().getY(),
	        (int) rct.getPt().getX() + (int) rct.getSide1(),
	        (int) rct.getPt().getY()
	        );
		g2d.drawLine(
		        (int) rct.getPt().getX() + (int) rct.getSide1(),
		        (int) rct.getPt().getY(),
		        (int) rct.getPt().getX() + (int) rct.getSide1(),
		        (int) rct.getPt().getY() + (int) rct.getSide2()
		        );
		g2d.drawLine(
		        (int) rct.getPt().getX() + (int) rct.getSide1(),
		        (int) rct.getPt().getY() + (int) rct.getSide2(),
		        (int) rct.getPt().getX(),
		        (int) rct.getPt().getY() + (int) rct.getSide2()
		        );
		g2d.drawLine(
		        (int) rct.getPt().getX(),
		        (int) rct.getPt().getY() + (int) rct.getSide2(),
		        (int) rct.getPt().getX(),
		        (int) rct.getPt().getY()
		        );

		
		//Draw Square.
        g2d.setColor(Color.cyan);
		g2d.drawLine(
				(int) sqr.getPt().getX(),
				(int) sqr.getPt().getY(),
				(int) sqr.getPt().getX() + (int) sqr.getSide1(),
				(int) sqr.getPt().getY()
				);
		g2d.drawLine(
		        (int) sqr.getPt().getX() + (int) sqr.getSide1(),
		        (int) sqr.getPt().getY(),
		        (int) sqr.getPt().getX() + (int) sqr.getSide1(),
		        (int) sqr.getPt().getY() + (int) sqr.getSide2()
		        );
		g2d.drawLine(
		        (int) sqr.getPt().getX() + (int) sqr.getSide1(),
		        (int) sqr.getPt().getY() + (int) sqr.getSide2(),
		        (int) sqr.getPt().getX(),
		        (int) sqr.getPt().getY() + (int) sqr.getSide2()
		        );
		g2d.drawLine(
		        (int) sqr.getPt().getX(),
		        (int) sqr.getPt().getY() + (int) sqr.getSide2(),
		        (int) sqr.getPt().getX(),
		        (int) sqr.getPt().getY()
		        );
        
        //Draw each BoundingBoxs and one for all BoundingBoxes
        //only if they exist, per drawBoundingBox method
        //defined in OurGUI class (not in BoundingBox class).
        //I suppose the same structure of using such a method
        //could be implemented for drawing the various Geometries, too
        //(e.g. drawCircle, etc.), but this may override native methods.
        drawBoundingBox(g2d, bbPnt); //Note that a bounding box for a Point is very small, smaller than the drawn oval.
        drawBoundingBox(g2d, bbCir);        
        drawBoundingBox(g2d, bbPb);
        drawBoundingBox(g2d, bbPln);
        drawBoundingBox(g2d, bbPlg);
        drawBoundingBox(g2d, bbRct);
        drawBoundingBox(g2d, bbSqr);
        drawBoundingBox(g2d, bbAll);
        
        //Draw user entered point.
        drawUserPoint(g2d,pUser);

        
	} //End PAINT

	
	

	

	
	
	
	
	
//====================================================================================
//MAIN Program (embedded)
//====================================================================================

	public static void main(String[] args) throws InterruptedException {

		
		//==================================================		
		//Create instance of OurGUI class and add components.
		
		OurGUI gui = new OurGUI();
		
		//Create rField, add rField to GUI for user specified radius.
		gui.rField =  new JTextField("Enter snap radius");
		gui.add(gui.rField);
		
		//Create snapButton, add snapButton to GUI, and register an ActionListener
		//for performing action of closing PolyLine
		//if end point is <= user specified radius.
		JButton snapButton =  new JButton("Snap PolyLine closed if within PointBuffer");
		gui.add(snapButton);
		snapButton.addActionListener(gui);

		//Create boundingBoxButton, add boundingBoxButton to GUI, and register an ActionListener
		//for performing creating BoundingBox for each object and one for all BoundingBoxes.
		JButton boundingBoxButton =  new JButton("Create BoundingBoxes");
		gui.add(boundingBoxButton);
		boundingBoxButton.addActionListener(gui);	
		
		//Create addPointButton, add addPointButton to GUI, and register an ActionListener
		//for performing action of adding points
		gui.addField =  new JTextField("Coords separated by space");
		gui.add(gui.addField);
		JButton addPointButton =  new JButton("Add Points");
		gui.add(addPointButton);
		addPointButton.addActionListener(gui);
		
		
		//===========================
		//Default GUI settings (begin)
		//Frame size is still hard-coded with setPreferredSize().

		//Create JFrame to place other components such as panels or buttons within.
		JFrame frame = new JFrame("Our GUI");

		//Add an instance of the MinimalDrawingGUI to the frame.
		frame.add(gui);
		
		//Set the size, arrange components, and display the frame.
		frame.pack();
		frame.setVisible(true);
		
		//Exit on close.
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		//Default GUI settings (end)
		//==========================


		//===================================		
		//Construct objects, print to console
		//Usually, you would do this somewhere else. (Where?)
		
		//Construct Point.
		gui.setPnt(new Point(200,200));
		System.out.println("");
		System.out.println("Point:");		
		System.out.println(gui.pnt);	
		
		
		
		//Construct Circle.
		gui.setCir(new Circle(new Point (65, 400), 25));
		System.out.println("");
		System.out.println("Circle:");
		System.out.println("Center: " + gui.cir.getPt());
		System.out.println("Radius: " + gui.cir.getRad());		

		//Construct PointBuffer.
		gui.setPb(new PointBuffer(new Point(200,200), 50));
		System.out.println("");
		System.out.println("PointBuffer:");
		System.out.println("Center: " + gui.pb.getPt());
		System.out.println("Radius: " + gui.pb.getRad());
		
		//Construct PolyLine.
		gui.setPln(new PolyLine()) ;
		gui.pln.add(new Point(200,200));
		gui.pln.add(new Point(200,400));
		gui.pln.add(new Point(400,400));
		gui.pln.add(new Point(250,200));		
		System.out.println("");
		System.out.println("Polyline:");
		for (int i=0; i < gui.pln.polypoints.size(); i++){
			System.out.println(gui.pln.polypoints.get(i));
		}
		
		//Construct Polygon.		
		gui.setPlg(new Polygon());
		gui.plg.add(new Point(50,150));
		gui.plg.add(new Point(60,175));
		gui.plg.add(new Point(50,200));
		gui.plg.add(new Point(75,190));
		gui.plg.add(new Point(100,200));
		gui.plg.add(new Point(90,175));
		gui.plg.add(new Point(100,150));
		gui.plg.add(new Point(75,160));
		gui.plg.add(new Point(50,150));
		System.out.println("");
		System.out.println("Polygon:");
		for (int i=0; i < gui.plg.polypoints.size(); i++){
			System.out.println(gui.plg.polypoints.get(i));
		}		
		
		//Construct Rectangle.
		gui.setRct(new Rectangle(new Point (60, 240), 50, 25));
		System.out.println("");
		System.out.println("Rectangle:");
		System.out.println("Upper-left: " + gui.rct.getPt());
		System.out.println("Height: " + gui.rct.getSide2());
		System.out.println("Width: " + gui.rct.getSide1());
		
		//Construct Square.
		gui.setSqr(new Square(new Point (60, 300), 50));		
		System.out.println("");
		System.out.println("Square:");
		System.out.println("Upper-left: " + gui.sqr.getPt());
		System.out.println("Height: " + gui.sqr.getSide2());
		System.out.println("Width: " + gui.sqr.getSide1());
	

		
	} //End MAIN


	
	
//====================================================================================
//METHODS for OurGUI class
//====================================================================================	
    //Draw user-entered point after a translation to deal with the coordinate shift
    public void drawUserPoint(Graphics2D g2d, Point pUser){
	 if (pUser !=null){	 
	  AffineTransform trans = AffineTransform.getTranslateInstance(0, (500-userY)-userY);
	  Point2D orig = new Point2D.Double(userX, userY);
	  Point2D translated = new Point2D.Double();
	  translated = trans.transform(orig, translated);
	  
      g2d.setColor(Color.black);
      g2d.fillOval(
      		(int) translated.getX(),
      		(int) translated.getY(),
      		(int) 5, 
      		(int) 5
      );
	 }
	}

	//Draw BoundingBox in the g2d Graphics2D object using the BoundingBox of a specified object.
	public void drawBoundingBox(Graphics2D g2d, BoundingBox bb){
		if (bb != null){

			System.out.println("");
			System.out.println("Bounding box:");
			System.out.println(bb.getBoundingBox());

			g2d.setColor(Color.gray);

			g2d.drawLine(
					(int) bb.getBoundingBox().get(0).getX(),
					(int) bb.getBoundingBox().get(0).getY(),
					(int) bb.getBoundingBox().get(0).getX(),
					(int) bb.getBoundingBox().get(1).getY()
					);
			g2d.drawLine(
					(int) bb.getBoundingBox().get(0).getX(),
					(int) bb.getBoundingBox().get(1).getY(),
					(int) bb.getBoundingBox().get(1).getX(),
					(int) bb.getBoundingBox().get(1).getY()
					);
			g2d.drawLine(
					(int) bb.getBoundingBox().get(1).getX(),
					(int) bb.getBoundingBox().get(1).getY(),
					(int) bb.getBoundingBox().get(1).getX(),
					(int) bb.getBoundingBox().get(0).getY()
					);
			g2d.drawLine(
					(int) bb.getBoundingBox().get(1).getX(),
					(int) bb.getBoundingBox().get(0).getY(),
					(int) bb.getBoundingBox().get(0).getX(),
					(int) bb.getBoundingBox().get(0).getY()
					);
		}     	  
	} 
	
	
	
	//Getters and Setters for OurGUI class
	
	public Point getPnt() {
		return pnt;
	}

	public void setPnt(Point pnt) {
		this.pnt = pnt;
	}

	public Circle getCir() {
		return cir;
	}

	public void setCir(Circle cir) {
		this.cir = cir;
	}	
	
	public PointBuffer getPb() {
		return pb;
	}

	public void setPb(PointBuffer pb) {
		this.pb = pb;
	}
	
	public PolyLine getPln() {
		return pln;
	}

	public void setPln(PolyLine pln) {
		this.pln = pln;
	}

	public Polygon getPlg() {
		return plg;
	}

	public void setPlg(Polygon plg) {
		this.plg = plg;
	}

	public Rectangle getRct() {
		return rct;
	}

	public void setRct(Rectangle rct) {
		this.rct = rct;
	}

	public Square getSqr() {
		return sqr;
	}

	public void setSqr(Square sqr) {
		this.sqr = sqr;
	}

	
	
	

	public PointBuffer getPbPln() {
		return pbPln;
	}

	public void setPbPln(PointBuffer pbPln) {
		this.pbPln = pbPln;
	}

	public JTextField getrField() {
		return rField;
	}

	public void setrField(JTextField rField) {
		this.rField = rField;
	}

	public double getPbRad() {
		return pbRad;
	}

	public void setPbRad(double pbRad) {
		this.pbRad = pbRad;
	}
	
	
	

	public BoundingBox getBbPln() {
		return bbPln;
	}

	public void setBbPln(BoundingBox bbPln) {
		this.bbPln = bbPln;
	}

	public BoundingBox getBbPb() {
		return bbPb;
	}

	public void setBbPb(BoundingBox bbPb) {
		this.bbPb = bbPb;
	}

	public BoundingBox getBbPnt() {
		return bbPnt;
	}

	public void setBbPnt(BoundingBox bbPnt) {
		this.bbPnt = bbPnt;
	}

	public BoundingBox getBbPlg() {
		return bbPlg;
	}

	public void setBbPlg(BoundingBox bbPlg) {
		this.bbPlg = bbPlg;
	}

	public BoundingBox getBbRct() {
		return bbRct;
	}

	public void setBbRct(BoundingBox bbRct) {
		this.bbRct = bbRct;
	}

	public BoundingBox getBbSqr() {
		return bbSqr;
	}

	public void setBbSqr(BoundingBox bbSqr) {
		this.bbSqr = bbSqr;
	}

	public BoundingBox getBbCir() {
		return bbCir;
	}

	public void setBbCir(BoundingBox bbCir) {
		this.bbCir = bbCir;
	}







  //End METHODS
	
		
} //End OurGUI CLASS
